import { Pager } from "../type";
import $config from "../config";
import $bridge from "@/static/js/dsbridge.js";
import CryptoJS from "crypto-js";
import { isReactive, toRaw } from "vue";

export const prefix0 = (n: number | string): string => String(+n > 9 ? n : `0${n}`);

export const includeSrc = (src: string) => {
  switch (true) {
    case isEmpty(src):
      return src;
    case new RegExp(`^${$config.URL_REMOTE_ASSETS}`).test(src):
      if ($config.OS_NAME === 'ios' && $config.OS_VERSION < 14) return src;
      return src + '?x-oss-process=image/format,webp';
    case /(^data:image)|(^blob:)|(^([a-z][a-z\d\+\-\.]*:)?\/\/)|(^\/.*\.(svg)$)|(^\/)|(^_doc\/)/.test(src):
      return src;
    case /^#server\//.test(src):
      return `${$config.URL_STATIC + src.replace(/^#server\//, "/")}?v=${$config.APP_VERSION}`;
    default:
      return `${$config.URL_REMOTE_ASSETS || ""}/${src}?x-oss-process=image/format,webp`;
  }
};

export const formatPhoneNumber = (phoneNumber: string): string =>
  isPhoneNumber(phoneNumber) ? phoneNumber.replace(/(\d{3})\d{6}(\d{2})/, "$1******$2") : "";

export const rand = (min: number, max: number): number => ~~(Math.random() * (max - min + 1) + min);

export const transformQueryString = (params: string | AnyObject) => {
  if (typeof params === "string") {
    const queryStrings: RegExpMatchArray | null = params.match(/[^?&]+=[^?&]+/g);
    return queryStrings ? Object.fromEntries(
      queryStrings.map(item => item.split(/^([^=]*)=*/).filter(item => item))
    ) : {};
  }
  if (typeof params === "object") {
    return Object.keys(params).filter(key => ![undefined, null].includes(params[key])).map(key => `${key}=${params[key]}`).join("&");
  }
  throw Error("Parameter error");
}

export const transformURL = (url: string, params?: { [key: string]: any }, hash?: string) =>
  url + (params ? `${url.includes("?") ? "&" : "?"}${transformQueryString(params)}` : "") + (hash ? `#${hash}` : "");

export const isPhoneNumber = (str: string): boolean => /^1[0-9]{10}$/.test(str)

export const isEmail = (str: string): boolean => /\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*/.test(str);

export const isIdCard = (str: string): boolean =>
  /^(^[1-9]\d{7}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}$)|(^[1-9]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])((\d{4})|\d{3}[Xx])$)$/.test(str);

export const isEmptyObject = (obj: AnyObject): obj is {} => (Object.getOwnPropertyNames(obj).length === 0);

export const sleep = (delay: number): Promise<number> => (new Promise(resolve => setTimeout(resolve, delay * 1000)));

/**
 * @param {number | string} time 
 * @param {string} format
 * @return Format Date
*/
export const formatDate = ({ format = "Y-M-D h:m:s", time }:
  { format?: string; time?: number | string }): string | null => {
  if (!time) return null;
  const date = new Date(typeof time === "number" ? (String(time).length === 13 ? time : time * 1000) : time),
    times = {
      Y: date.getFullYear(),
      M: date.getMonth() + 1,
      D: date.getDate(),
      W: date.getDay(),
      h: date.getHours(),
      m: date.getMinutes(),
      s: date.getSeconds()
    }
  return format
    .replace("Y", String(times.Y))
    .replace("M", prefix0(times.M))
    .replace("D", prefix0(times.D))
    .replace("W", String(times.W))
    .replace("h", prefix0(times.h))
    .replace("m", prefix0(times.m))
    .replace("s", prefix0(times.s));
}

/**
* @param {Date} start
* @param {Date} end
* @returns {Array<string>}
*/
export const formatEveryDay = (start: Date, end: Date, format: undefined | string = undefined): Array<any> => {
  let dateList = [];
  while ((end.getTime() - start.getTime()) >= 0) {
    dateList.push(formatDate({ format, time: start.getTime() }));
    start.setDate(start.getDate() + 1);
  }
  return dateList;
}

export const formatDuration = ({ format = "D天h时m分s秒", seconds = 0 }) => {
  if (seconds <= 0) {
    return 0;
  }
  let [day, hour, minute, second] = [0, 0, 0, 0];
  day = ~~(seconds / (60 * 60 * 24));
  hour = ~~(seconds / (60 * 60)) - day * 24;
  const showHour = format.includes("D") ? hour : ~~(seconds / (60 * 60));
  minute = ~~(seconds / 60) - hour * 60 - day * 24 * 60;
  second = ~~seconds - day * 24 * 60 * 60 - hour * 60 * 60 - minute * 60;

  return format
    .replace("D", prefix0(day))
    .replace("h", prefix0(showHour))
    .replace("m", prefix0(minute))
    .replace("s", prefix0(second));
};

export const parseJSON = (str: string): AnyObject | null => {
  try {
    return JSON.parse(str);
  } catch (error) {
    return null;
  }
}

export const sortLetter = (letters: string) => [...letters.replace(/,/g, "")]
  .sort((a, b) => Number(a.charCodeAt(0).toString(16)) - Number(b.charCodeAt(0).toString(16)))
  .join(",")

/**
 * Determines whether the specified URL is absolute
 *
 * @param {string} url The URL to test
 * @returns {boolean} True if the specified URL is absolute, otherwise false
 */
export const isAbsoluteURL = (url: string): boolean => /^([a-z][a-z\d\+\-\.]*:)?\/\//i.test(url);

/**
 * 防抖: 频繁调用函数时重置上次延迟再执行（e.g. 按回城）
 * 
 * @param {Function} fn 防抖函数
 * @param {delay} delay 延迟时间
 * @param {boolean} immediate 是否立即执行
 * @returns {Function} 返回执行任务,附带cancel方法
 */
export const debounce = (fn: Function, delay = 300, immediate = true) => {
  let timer: null | number | NodeJS.Timeout = null;

  const task = (...args: any[]) => {
    immediate && !timer && fn(...args);

    if (timer) {
      clearTimeout(<number>timer);
      timer = null;
    }

    if (immediate) {
      timer = setTimeout(() => { timer = null }, delay);
      return;
    }

    timer = setTimeout(() => fn(...args), delay);
  };

  task.cancel = () => {
    if (timer) {
      clearTimeout(<number>timer);
      timer = null;
    }
  }

  return task;
}

/**
 * 节流: 频繁调用函数时按固定延迟时间执行（e.g. 按技能）
 * 
 * @param {Function} fn 节流函数
 * @param {delay} delay 延迟时间
 * @param {boolean} immediate 是否立即执行
 * @returns {Function} 返回执行任务,附带cancel方法
 */
export const throttle = (fn: Function, delay = 300, immediate = true) => {
  let timer: null | number | NodeJS.Timeout;

  const task = (...args: any[]) => {
    immediate && timer === undefined && fn(...args);
    !timer && (timer = setTimeout(() => {
      fn(...args);
      timer = null;
    }, delay));
  }

  task.cancel = () => {
    if (timer) {
      clearTimeout(<number>timer);
      timer = null;
    }
  }

  return task;
}

export const uniNavigator = ({ type = "navigateTo", url, animationType, animationDuration, events }: {
  type?: "reLaunch" | "navigateTo" | "switchTab" | "redirectTo" | "navigateBack";
  url?: string;
  animationType?: 'auto' | 'none' | 'slide-in-right' | 'slide-in-left' | 'slide-in-top' | 'slide-in-bottom' | 'fade-in' | 'zoom-out' | 'zoom-fade-out' | 'pop-in';
  animationDuration?: number;
  events?: any;
}) => {
  if (isAbsoluteURL(String(url))) {
    switch (getClient()) {
      case 1:
      case 2:
      case 3:
      case 4:
        location.href = String(url);
        break;
      case 5:
        plus.runtime.launchApplication({ action: url }, (e) => { console.log(e); });
        break;
      case 6:
        plus.runtime.openURL(url!, (e) => { console.log(e); });
        break;
      case 9:
      case 10:
        $bridge.call?.("fnTool.openUrl", { url });
        break;
      default:
        uni.navigateTo({
          url: transformURL("/pages/common/webview",
            { url: encodeURIComponent(url!) }
          )
        });
        break;
    }
    return;
  }

  /^\/pages\/client\/tabs\/.*/.test(url!) && (type = "switchTab");

  if (type === "navigateBack") {
    const pages = getCurrentPages();
    return pages.length > 1 ? uni.navigateBack()
      : uni.reLaunch({ url: $config.PAGE_HOME_CLIENT });
  }

  return {
    reLaunch: () => url && uni.reLaunch({ url, }),
    navigateTo: () => url && uni.navigateTo({ url, animationType, animationDuration, events }),
    switchTab: () => url && uni.switchTab({ url, }),
    redirectTo: () => url && uni.redirectTo({ url, }),
  }[type]();
}


export const uniPreviewImage = (options: UniApp.PreviewImageOptions) => {
  uni.previewImage(options);
}

export const uniMakePhoneCall = (options: UniApp.MakePhoneCallOptions) => {
  options.phoneNumber && uni.makePhoneCall(options);
}

export const uniCopyText = (options: UniApp.SetClipboardDataOptions) => {
  uni.setClipboardData(options);
}

export const uniShowToast = (options: UniApp.ShowToastOptions) => {
  return uni.showToast(options);
}

export const unShowModal = (options: UniApp.ShowModalOptions) => {
  return uni.showModal(options);
}

export const getClient = () => {
  const { OS_NAME, PLATFORM } = $config;

  if (PLATFORM === "web") {

    const agent = navigator.userAgent.toLowerCase();

    if (agent.includes($config.PACKAGE_NAME)) {
      return OS_NAME === "ios" ? 9 : 10;
    }

    //微信内部
    if (agent.includes("micromessenger")) {
      return OS_NAME === "ios" ? 1 : 2;
    }

    //其他浏览器
    return OS_NAME === "ios" ? 3 : 4;
  }

  //APP
  if (PLATFORM === "app") {
    return OS_NAME === "ios" ? 5 : 6;
  }

  //其他(小程序)
  return OS_NAME === "ios" ? 7 : 8;
}

export const wxJsPay = (payParams: any, callBack: AnyObject = {}): void => {
  WeixinJSBridge.invoke(
    "getBrandWCPayRequest",
    payParams,
    ({ err_msg }: any) => (
      callBack.complete(),
      err_msg == "get_brand_wcpay_request:ok" ?
        uni.showToast({ title: "支付成功", complete: callBack.success }) :
        uni.showToast({ title: "支付失败", icon: "none", complete: callBack.fail })
    )
  );
}

export const guid = (len = 32, firstU = true, radix = null as null | number) => {
  let chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('');
  let guid = [];
  radix = radix || chars.length;

  if (len) {
    // 如果指定guid长度,只是取随机的字符,0|x为位运算,能去掉x的小数位,返回整数位
    for (let i = 0; i < len; i++) guid[i] = chars[0 | Math.random() * radix];
  } else {
    let r;
    // rfc4122标准要求返回的guid中,某些位为固定的字符
    guid[8] = guid[13] = guid[18] = guid[23] = '-';
    guid[14] = '4';

    for (let i = 0; i < 36; i++) {
      if (!guid[i]) {
        r = 0 | Math.random() * 16;
        guid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r];
      }
    }
  }
  // 移除第一个字符,并用u替代,因为第一个字符为数值时,该gguid不能用作id或者class
  if (firstU) {
    guid.shift();
    return 'u' + guid.join('');
  }
  return guid.join('');
}

export const pickObjectProperty = (keys: string[], obj: AnyObject) => {
  const filterObj: AnyObject = {};
  Object.keys(obj).filter(key => keys.includes(key)).forEach(key => {
    filterObj[key] = obj[key];
  });

  return filterObj;
}

export const toFixed_adjust = (number: number, count: number) => {
  const [integer, decimal] = String(number).split(".");

  if (count) {
    const placeholder = Array(count).fill(0).join("");
    return decimal ? `${integer}.${(decimal + placeholder).slice(0, count)}` : `${integer}.${placeholder}`;
  }

  return number.toString();
}

export const encryptData = (data: AnyObject, randomStr: string, timestamp: string, token: string) => {
  const { MD5, AES: { encrypt }, mode: { CBC }, pad: { Pkcs7 }, enc: { Utf8 } } = CryptoJS;
  for (let key in data) data[key] ?? delete data[key];
  const sign = [$config.APP_KEY, token, $config.OS_NAME, $config.APP_VERSION_CODE, timestamp, $config.DEVICE_ID, $config.APP_KEY].join("");
  Object.assign(data, { signstr: MD5(sign).toString().toLocaleUpperCase() });
  return {
    encrypt_str: encrypt(JSON.stringify(data),
      Utf8.parse(randomStr), {
      iv: Utf8.parse("b81400d9b07a4634"),
      mode: CBC,
      padding: Pkcs7
    }).toString()
  };
}

/**
 * @description 列表数据分页，渲染当页数据，缓存下页数据
 * @param {UniNamespace.RequestOptions} options 请求体参数
 * @param {Pager} reactiveData 渲染列表响应数据
 * @param {string} listKey 响应列表键
 * @param {string} method 列表追加方式
 * @return {void} void
 */
export const pager = <T = AnyObject>(options: UniNamespace.RequestOptions, reactiveData: Pager<T>, listKey = "", method: "push" | "unshift" = "push") => (
  new Promise<any>((resolve, reject) => {
    if (!isReactive(reactiveData)) return reject(`The incoming data is not a "reactive" data.`);
    if (reactiveData.cache.list.length) {
      !reactiveData.page ? (reactiveData.list = reactiveData.cache.list) : reactiveData.list[method](...reactiveData.cache.list);
      reactiveData.page = reactiveData.cache.page;
      resolve(toRaw(reactiveData.cache.list));
      reactiveData.cache.list = [];
    }
    reactiveData.next = reactiveData.cache.page > reactiveData.page;
    if (!reactiveData.cache.page || reactiveData.cache.status === "loading") {
      reactiveData.next = true;
      return "No more or loading.";
    }
    reactiveData.cache.status = "loading";
    reactiveData.limit = reactiveData.limit || 20;

    uni.request({
      url: options.url,
      method: options.method,
      data: {
        page: reactiveData.cache.page,
        limit: reactiveData.limit,
        ...(options.data as AnyObject || {})
      },
      success({ data }: AnyObject) {
        const list = listKey ? data[listKey] : data;
        if (!Array.isArray(list)) return reject("Response data exception.");
        const isLastPage = list.length < reactiveData.limit!;
        reactiveData.cache.status = isLastPage ? "nomore" : "loadmore";
        reactiveData.cache.page = isLastPage ? 0 : reactiveData.cache.page + 1;
        if (!reactiveData.page && isLastPage) {
          reactiveData.list = list;
          return resolve(list);
        }
        reactiveData.cache.list = list;
        reactiveData.next && pager(options, reactiveData, listKey, method);
        resolve(list);
      }
    });
  })
)

/**
 * @description 去除空格
 * @param String str 需要去除空格的字符串
 * @param String pos both(左右)|left|right|all 默认both
 */
export const trim = (str: string, pos = 'both') => {
  str = String(str)
  if (pos == 'both') {
    return str.replace(/^\s+|\s+$/g, '')
  }
  if (pos == 'left') {
    return str.replace(/^\s*/, '')
  }
  if (pos == 'right') {
    return str.replace(/(\s*$)/g, '')
  }
  if (pos == 'all') {
    return str.replace(/\s+/g, '')
  }
  return str
}

/**
 * @description 深度克隆
 * @param {object} obj 需要深度克隆的对象
 * @returns {*} 克隆后的对象或者原值（不是对象）
 */
export function deepClone(obj: AnyObject) {
  // 对常见的“非”值，直接返回原来值
  if ([null, undefined, NaN, false].includes(obj as any)) return obj;
  if (typeof obj !== 'object' && typeof obj !== 'function') {
    // 原始类型直接返回
    return obj
  }
  const o: AnyObject = Array.isArray(obj) ? [] : {}
  for (const i in obj) {
    if (obj.hasOwnProperty(i)) {
      o[i] = typeof obj[i] === 'object' ? deepClone(obj[i]) : obj[i]
    }
  }
  return o;
}

/**
 * @description JS对象深度合并
 * @param {object} target 需要拷贝的对象
 * @param {object} source 拷贝的来源对象
 * @returns {object|boolean} 深度合并后的对象或者false（入参有不是对象）
 */
export function deepMerge(target: AnyObject = {}, source: AnyObject = {}) {
  target = deepClone(target)
  if (typeof target !== 'object' || typeof source !== 'object') return false
  for (const prop in source) {
    if (!source.hasOwnProperty(prop)) continue
    if (prop in target) {
      if (typeof target[prop] !== 'object') {
        target[prop] = source[prop]
      } else if (typeof source[prop] !== 'object') {
        target[prop] = source[prop]
      } else if (target[prop].concat && source[prop].concat) {
        target[prop] = target[prop].concat(source[prop])
      } else {
        target[prop] = deepMerge(target[prop], source[prop])
      }
    } else {
      target[prop] = source[prop]
    }
  }
  return target;
}

/**
* 判断是否为空
*/
export function isEmpty(value: any) {
  switch (typeof value) {
    case 'undefined':
      return true
    case 'string':
      if (value.replace(/(^[ \t\n\r]*)|([ \t\n\r]*$)/g, '').length == 0) return true
      break
    case 'boolean':
      if (!value) return true
      break
    case 'number':
      if (value === 0 || isNaN(value)) return true
      break
    case 'object':
      if (value === null || value.length === 0) return true
      for (const i in value) {
        return false
      }
      return true
  }
  return false
}

/**
 * import cities1 from "@/static/server/cities1.json";
 * import { pinyin } from 'pinyin-pro';
 * console.log(cities1.sort(customSort));
  */
export const customSort = (a: AnyObject, b: AnyObject) => {

  const pinyin: any = () => { };

  // 对于相同 role 的元素，按照 nameCard 或 nick 转成汉语拼音
  const aPinyin = pinyin(a.name, { toneType: 'none' }).replace(/\s/g, '');
  const bPinyin = pinyin(b.name, { toneType: 'none' }).replace(/\s/g, '');

  // 判断 a 和 b 是否为特殊符号或数字
  const isSpecialA = /[\W\d]/.test(aPinyin[0]);
  const isSpecialB = /[\W\d]/.test(bPinyin[0]);

  // 如果 a 和 b 都是特殊符号或数字，按原始顺序排序
  if (isSpecialA && isSpecialB) {
    return 0;
  }

  // 如果 a 是特殊符号或数字，将其排在 b 后面
  if (isSpecialA) {
    return 1;
  }

  // 如果 b 是特殊符号或数字，将其排在 a 后面
  if (isSpecialB) {
    return -1;
  }

  return aPinyin.localeCompare(bPinyin);
};


/**
  * console.log(JSON.stringify(pySegSort(cities1, "city_name")));
 */
export const pySegSort = (arr: AnyObject[], name = "name") => {
  if (!String.prototype.localeCompare) return null
  // const letters = 'abcdefghjklmnopqrstwxyz'.split('')
  const letters = 'ABCDEFGHJKLMNOPQRSTWXYZ'.split('')
  const zh = '阿八嚓哒妸发旮哈讥咔垃痳拏噢妑七呥扨它穵夕丫帀'.split('')
  const segs: AnyObject[] = [];
  letters.forEach((item, i) => {
    let cur = { letter: item, data: [] as AnyObject[] }
    arr.forEach((item: any) => {
      if (item[name].localeCompare(zh[i]) >= 0 && item[name].localeCompare(zh[i + 1]) < 0) {
        cur.data.push(item)
      }
    })

    if (cur.data.length) {
      cur.data.sort(function (a, b) {
        return a[name].localeCompare(b[name], 'zh')
      })
      segs.push(cur)
    }
  })

  return segs;
}